#include "SCDirWatcher.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#ifdef OS_LINUX
// linux
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

struct SCDirWatcher::DirInfo_t {
	std::string	path;
};

#define SC_HANDLE_CLOSE	close
#define closeDirInfo(pinfo, fd)	delete pinfo
#elif defined(OS_MAC)
// mac
#else
#include <atlstr.h>
#define SC_IOCOMPLETE_PORT	1235

#ifndef container_of
# define container_of(ptr, type, member) \
	((type *) ((char *) (ptr) - offsetof(type, member)))
#endif

#define SC_HANDLE_CLOSE	CloseHandle
struct SCDirWatcher::DirInfo_t {
	OVERLAPPED	ol;
	std::string	path;
	char		notify[1024];
	std::set<std::wstring> mSet;
};

inline static void closeDirInfo(SCDirWatcher::DirInfo_t* pinfo, HANDLE fd)
{
	CloseHandle(pinfo->ol.hEvent);
	CloseHandle(fd);
	delete pinfo;
}

#endif // end platform

SCDirWatcher::SCDirWatcher(SCDirWatcher::WatcherProto* pproto):
        m_proto(pproto)
{
#ifdef OS_LINUX
	// linux
	m_ihandle = inotify_init1(IN_NONBLOCK);
#elif defined(OS_MAC)
	// mac
#else
	// windows
	m_ihandle = SC_INVALID_HANDLE_VALUE;
#endif
}

SCDirWatcher::~SCDirWatcher()
{
	SCDirWatcher::DirInfo_t* pinfo;
	SC_DIR_MAP::iterator it;
	if (SC_INVALID_HANDLE_VALUE != m_ihandle) {
		SC_HANDLE_CLOSE(m_ihandle);
		// 关闭所有已经打开的port	
		for(it = m_dirMap.begin(); it != m_dirMap.end(); ++it) {
			pinfo = it->second;
			closeDirInfo(pinfo, it->first);
		}
	}
	m_proto = NULL;
}

void SCDirWatcher::updateOnce()
{
	if (!m_proto || SC_INVALID_HANDLE_VALUE == m_ihandle) return;
	if (m_dirMap.empty()) return;
#ifdef OS_LINUX
	char buf[1024];
	struct inotify_event* pevent;
	ssize_t n = read(m_ihandle, buf, 1024);
	if (n < 0) return;
	size_t offset = 0;
	size_t tlen = 0;
	size_t tEvLen = sizeof(struct inotify_event) + 512;
	struct inotify_event* tmpEvent = (struct inotify_event*)alloca(tEvLen);
	memset(tmpEvent, 0, tEvLen);
	std::vector<FState> stVec;
	while (n > 0) {
		offset = 0;
		while (offset < n) {
			pevent = (struct inotify_event*)(buf + offset);
			tlen = sizeof(struct inotify_event) + pevent->len;
			offset += tlen;
			if (tryAddFState(stVec, pevent, tmpEvent, false)) {
				memcpy(tmpEvent, pevent, tlen);
				tmpEvent->name[tmpEvent->len] = 0;
			} else {
				memset(tmpEvent, 0, tEvLen);
			}
		}
		n = read(m_ihandle, buf, 1024);
	}
	tryAddFState(stVec, pevent, tmpEvent, true);
	for(size_t i = 0; i < stVec.size(); ++i) {
		if (stVec[i].flag & SCDirWatcher::Dir) {
			m_proto->onDirWatch(stVec[i]);
		} else {
			m_proto->onFileWatch(stVec[i]);
		}
	}
#elif defined(OS_MAC)
	// mac
#else
	DirInfo_t* pinfo;
	LPOVERLAPPED outOl;
	ULONG_PTR outKey;
	DWORD cbBytes;
	HANDLE fd;	
	for (SC_DIR_MAP::iterator it = m_dirMap.begin(); it != m_dirMap.end();) {
		fd = it->first;
		pinfo = it->second;
		if (!ReadDirectoryChangesW(fd, &pinfo->notify, sizeof(pinfo->notify), FALSE,
			FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_FILE_NAME |
			      FILE_NOTIFY_CHANGE_LAST_WRITE, NULL, &pinfo->ol, NULL)) {
				      m_descMap.erase(pinfo->path);
				      m_dirMap.erase(it++);
				      m_proto->onWatchLose(pinfo->path);
				      closeDirInfo(pinfo, fd);
		} else {
			++it;
		}
	}
	if (GetQueuedCompletionStatus(m_ihandle, &cbBytes, &outKey, &outOl, INFINITE)) {
			pinfo = container_of(outOl, DirInfo_t, ol);
			initFStateFromDirInfo(pinfo);
	} else {
		fprintf(stderr, "出现了一些异常情况\n");
	}
#endif
}

bool SCDirWatcher::add(std::string path)
{
	if (path.empty() || path.length() > 384)
		return false;
	if (path.at(path.length() - 1) == SC_SEPERATOR_CHAR)
		path[path.length() - 1] = 0;
	if (m_descMap.find(path) != m_descMap.end())
		return true;
	SC_IHANDLE desc;
	SC_DIR_MAP::iterator it;
	SCDirWatcher::DirInfo_t* pinfo;
#ifdef OS_LINUX
	// linux
	if (SC_INVALID_HANDLE_VALUE == m_ihandle)
		return false;
	struct stat tmpSt;
	if (stat(path.c_str(), &tmpSt) < 0)
		return false;
	if (!S_ISDIR(tmpSt.st_mode))
		return false;
	desc = inotify_add_watch(m_ihandle, path.c_str(), IN_DELETE_SELF | IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVE);
	if (SC_INVALID_HANDLE_VALUE == desc)
		return false;
#elif defined(OS_MAC)
	// mac
#else
	// windows
	CString dirPath(path.c_str());
	desc = CreateFile(dirPath, FILE_LIST_DIRECTORY,
		FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 
		NULL, OPEN_EXISTING, 
		FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED ,
		NULL);
#endif // end platform
	if (SC_INVALID_HANDLE_VALUE == desc)
		return false;
	pinfo = new DirInfo_t;
#ifdef OS_WIN
	SC_IHANDLE tmp;
	if (SC_INVALID_HANDLE_VALUE == m_ihandle) {
		tmp = m_ihandle = CreateIoCompletionPort(desc, NULL, SC_IOCOMPLETE_PORT, 64);
	} else {
		tmp = CreateIoCompletionPort(desc, m_ihandle, SC_IOCOMPLETE_PORT, 64);
	}
	if (SC_INVALID_HANDLE_VALUE == tmp) {
		fprintf(stderr, "超过监控文件夹数量上限\n");
		CloseHandle(desc);
		delete pinfo;
		return false;
	}
	pinfo->ol.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
	pinfo->ol.Offset = pinfo->ol.OffsetHigh = 0;
#endif // end platform
	pinfo->path = path;
	it = m_dirMap.insert(m_dirMap.begin(), std::make_pair(desc, pinfo));
	m_descMap.insert(std::make_pair(path, it));
	return true;
}

bool SCDirWatcher::remove(std::string path)
{
	if (SC_INVALID_HANDLE_VALUE == m_ihandle || path.empty() || m_descMap.empty())
		return false;
	if (path.at(path.length() - 1) == SC_SEPERATOR_CHAR)
		path[path.length() - 1] = 0;
	SC_DESC_MAP::iterator it = m_descMap.find(path);
	if (it != m_descMap.end()) {
		SC_IHANDLE desc = it->second->first;
		DirInfo_t* pinfo = it->second->second;
#ifdef OS_LINUX
		if (-1 == inotify_rm_watch(m_ihandle, desc))
			return false;
#elif defined(OS_MAC)
	// mac
#else
		CloseHandle(pinfo->ol.hEvent);
		CloseHandle(desc);
#endif
		delete pinfo;
		m_dirMap.erase(it->second);
		m_descMap.erase(it);
		return true;
	}
	return false;
}

bool SCDirWatcher::dirs(std::vector<std::string>& out)
{
	out.clear();
	if (SC_INVALID_HANDLE_VALUE == m_ihandle)
		return false;
	for (SC_DESC_MAP::iterator it = m_descMap.begin(); it != m_descMap.end(); ++it) {
		out.push_back(it->first);
	}
	return true;
}

#ifdef OS_LINUX
void SCDirWatcher::getFileFullName(const struct inotify_event* pe, SCDirWatcher::FState& st,
				    bool newOrOld)
{
	std::string* ps = newOrOld ? &st.newName : &st.oldName;
	DirInfo_t* pinfo = m_dirMap.find(pe->wd)->second;
	*ps = pinfo->path;
	ps->append("/");
	ps->append(pe->name);
}

#define SC_STEASY_SET(st, pe, fg) \
	st.flag = fg;\
	if (pe->mask & IN_ISDIR) {\
		st.flag |= SCDirWatcher::Dir;\
		getFileFullName(pe, st, false);\
	} else \
		getFileFullName(pe, st, false)

bool SCDirWatcher::normalFStateCheck(SCDirWatcher::FState& st,  const struct inotify_event* pe)
{
	if (pe->mask & IN_DELETE_SELF) {
		SC_DIR_MAP::iterator it =  m_dirMap.find(pe->wd);
		DirInfo_t* pinfo = it->second;
		m_proto->onWatchLose(pinfo->path);
		closeDirInfo(pinfo, it->first);
		m_dirMap.erase(it);
		return false;
	}
	if (pe->mask & IN_CREATE)
		st.flag |= SCDirWatcher::New;
	if (pe->mask & IN_DELETE)
		st.flag |= SCDirWatcher::Del;
	if (pe->mask & IN_MODIFY)
		st.flag |= SCDirWatcher::Mod;
	if (pe->mask & IN_ISDIR) {
		st.flag |= SCDirWatcher::Dir;
		getFileFullName(pe, st, false);
	} else {
		getFileFullName(pe, st, false);
	}
	return true;
}

bool SCDirWatcher::tryAddFState(std::vector<SCDirWatcher::FState>& vec, const struct inotify_event* pe,
			 const struct inotify_event* pl, bool endCheck)
{
	if (m_dirMap.find(pe->wd) == m_dirMap.end())
		return false;
	SCDirWatcher::FState st;
	st.zero();
	if (endCheck) {
		if (pl->cookie) {
			if (pl->mask & IN_MOVED_FROM)
				st.flag = SCDirWatcher::Del;
			else
				st.flag = SCDirWatcher::New;
			if (pl->mask & IN_ISDIR) {
				getFileFullName(pl, st, false);
				st.flag |= SCDirWatcher::Dir;
			} else {
				getFileFullName(pl, st, false);
			}
			vec.push_back(st);
		}
		return false;
	}
	if (pl->cookie) {
		if (pe->cookie) {
			if (pe->cookie == pl->cookie) {
				st.flag = SCDirWatcher::RNa;
				if (pe->mask & IN_ISDIR) {
					st.flag |= SCDirWatcher::Dir;
					getFileFullName(pl, st, false);
					getFileFullName(pe, st, true);
				} else {
					getFileFullName(pl, st, false);
					getFileFullName(pe, st, true);
				}
				vec.push_back(st);
				return false;
			}
			if (pe->mask & IN_MOVED_FROM) {
				SC_STEASY_SET(st, pe, SCDirWatcher::Del);
				vec.push_back(st);
				return true;
			} else {
				SC_STEASY_SET(st, pe, SCDirWatcher::Del);
				vec.push_back(st);
				SC_STEASY_SET(st, pe, SCDirWatcher::New);
				vec.push_back(st);
				return false;
			}
		} else {
			SC_STEASY_SET(st, pe, SCDirWatcher::Del);
			vec.push_back(st);
			st.zero();
		}
	} else if (pe->cookie) {
		if (pe->mask & IN_MOVED_FROM)
			return true;
		if (pe->mask & IN_MOVED_TO) {
			SC_STEASY_SET(st, pe, SCDirWatcher::New);
			vec.push_back(st);
			return false;
		}
	}
	// 常规处理
	if (normalFStateCheck(st, pe))
		vec.push_back(st);
	return false;
}

#elif defined(__APPLE__)
#else
void SCDirWatcher::initFStateFromDirInfo(DirInfo_t* pinfo)
{
	if (!pinfo)  return;
	SCDirWatcher::FState fst;
	fst.zero();
	PFILE_NOTIFY_INFORMATION tmp;
	size_t offset = 0;
	bool doAction = true;
	do {
		tmp = (PFILE_NOTIFY_INFORMATION)(pinfo->notify + offset);
		switch (tmp->Action)
		{
		case FILE_ACTION_ADDED:
			fst.flag = SCDirWatcher::New;
			getFileFullName(pinfo->path, tmp->FileName, tmp->FileNameLength , fst.oldName);
			break;
		case FILE_ACTION_MODIFIED:
		{
			std::wstring idx = std::wstring(tmp->FileName, tmp->FileNameLength / 2);
			std::set<std::wstring>::iterator it = pinfo->mSet.find(idx);
			if (it != pinfo->mSet.end()) {
				doAction = false;
				pinfo->mSet.erase(it);
				break;
			}
			pinfo->mSet.insert(idx);
			fst.flag = SCDirWatcher::Mod;
			getFileFullName(pinfo->path, tmp->FileName, tmp->FileNameLength, fst.oldName);
		}
			break;
		case FILE_ACTION_REMOVED:
			fst.flag = SCDirWatcher::Del;
			getFileFullName(pinfo->path, tmp->FileName, tmp->FileNameLength, fst.oldName);
			break;
		case FILE_ACTION_RENAMED_OLD_NAME:
			doAction = false;
			getFileFullName(pinfo->path, tmp->FileName, tmp->FileNameLength, fst.oldName);
			break;
		case FILE_ACTION_RENAMED_NEW_NAME:
			fst.flag = SCDirWatcher::RNa;
			getFileFullName(pinfo->path, tmp->FileName, tmp->FileNameLength, fst.newName);
			break;
		}
		if (doAction)  {
			m_proto->onFileWatch(fst);
			fst.zero();
		} else {
			doAction = true;
		}
		offset += tmp->NextEntryOffset;
	} while(tmp->NextEntryOffset);
}

void SCDirWatcher::getFileFullName(const std::string& dpath, const wchar_t* name, DWORD namel, std::string& out)
{
	namel += 1;
	char* psText = (char*)alloca(namel);
	memset(psText, 0, namel);
	WideCharToMultiByte(CP_OEMCP, NULL, name, namel / 2, psText, namel, NULL, FALSE);	
	out = dpath + "\\" + psText;
}

#endif
